/*
 * El engine debe contener las reglas y metodos que lleven a la solución
 */
package Inference;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 *
 * @author Adrian
 */
public class Engine {
    
    private String usuario;
    private ArrayList<String> juegosRecomendados= new ArrayList<>();
    private ArrayList<String> juegosComprados= new ArrayList<>();
    private ArrayList<String> juegosJugados= new ArrayList<>();
    private ArrayList<String> juegos= new ArrayList<>();
    private ArrayList<String> juegosCandidatos= new ArrayList<>();
    private int numJuegosTotales=0;
    private HashMap contadores= new HashMap();
    private HashMap estrellas= new HashMap();
    private ArrayList<String> consolas= new ArrayList<>();
    private ModuloExplicacion mExplicacion= new ModuloExplicacion();
    
    public Engine(){
        
    }
    
    /**
     * @return the usuario
     */
    public String getUsuario() {
        return usuario;
    }

    /**
     * @param usuario the usuario to set
     */
    public void setUsuario(String usuario) {
        this.usuario = usuario;
    }

    /**
     * @return the juegosRecomendados
     */
    public ArrayList<String> getJuegosRecomendados() {
        return juegosRecomendados;
    }

    /**
     * @param juegosRecomendados the juegosRecomendados to set
     */
    public void setJuegosRecomendados(ArrayList<String> juegosRecomendados) {
        this.juegosRecomendados = juegosRecomendados;
    }

    /**
     * @return the juegosComprados
     */
    public ArrayList<String> getJuegosComprados() {
        return juegosComprados;
    }

    /**
     * @param juegosComprados the juegosComprados to set
     */
    public void setJuegosComprados(ArrayList<String> juegosComprados) {
        this.juegosComprados = juegosComprados;
    }

    /**
     * @return the juegosJugados
     */
    public ArrayList<String> getJuegosJugados() {
        return juegosJugados;
    }

    /**
     * @param juegosJugados the juegosJugados to set
     */
    public void setJuegosJugados(ArrayList<String> juegosJugados) {
        this.juegosJugados = juegosJugados;
    }

    /**
     * @return the numJuegosTotales
     */
    public int getNumJuegosTotales() {
        return numJuegosTotales;
    }

    /**
     * @param numJuegosTotales the numJuegosTotales to set
     */
    public void setNumJuegosTotales(int numJuegosTotales) {
        this.numJuegosTotales = numJuegosTotales;
    }

    /**
     * @return the contadores
     */
    public HashMap getContadores() {
        return contadores;
    }

    /**
     * @param contadores the contadores to set
     */
    public void setContadores(HashMap contadores) {
        this.contadores = contadores;
    }
    
    //Tipo por ejemplo contadorGenero, contadorEstudio, etc
    public void crearTipoContador(String tipoContador){
        
        HashMap contadordetipo= new HashMap();
        
        if (contadordetipo.get(tipoContador)==null)
            this.contadores.put(tipoContador, contadordetipo);
        
    }
    
    public HashMap getTipoContador(String tipoContador){
        
        return (HashMap)(this.contadores.get(tipoContador));
    }
    
    public void imprimirTipoContador(String tipoContador){
        
        HashMap contadordetipo=this.getTipoContador(tipoContador);
        Object[] llave = contadordetipo.keySet().toArray();

        for (int i=0;llave!=null && i<llave.length;i++){
            
            System.out.println(llave[i].toString() + ": " + this.getValorContador(tipoContador, llave[i].toString()));
        }
    }
    
    //contador es como RPG, FPS, etc
    public void crearContador(String tipoContador, String contador){
        
        HashMap contadordetipo=getTipoContador(tipoContador);
        
        if (contadordetipo.get(contador)==null)        
            contadordetipo.put(contador, 0);
                
    }
    
    public int getValorContador(String tipoContador, String contador){
        
        HashMap contadordetipo=getTipoContador(tipoContador);
        
        return (int)(contadordetipo.get(contador)==null?0:contadordetipo.get(contador));
    }
    
    
    public void aumentaContador(String tipoContador, String contador){
        
       aumentaContador(tipoContador, contador, 1);
    }
    
    public void aumentaContador(String tipoContador, String contador, int aumento){
        
       HashMap contadordetipo= getTipoContador(tipoContador);
       
       if (contadordetipo!=null && contador!=null && !contador.trim().equals(""))
           contadordetipo.put(contador, this.getValorContador(tipoContador, contador) +aumento );       
           
    }
    
    public void setContador(String tipoContador, String contador, int valor){
        
       HashMap contadordetipo= getTipoContador(tipoContador);
       
       if (contadordetipo!=null)
           contadordetipo.put(contador, valor );
    }
    
    public void aumentaContadorLista(String tipoContador, List<String> lista){
        
        for (int i=0; i<lista.size(); i++){
            
            this.aumentaContador(tipoContador, lista.get(i));
        }
        
    }
    
    public void aumentaContadorLista(String tipoContador, List<String> lista, int cantidad){
        
        for (int i=0; i<lista.size(); i++){
            
            this.aumentaContador(tipoContador, lista.get(i),cantidad);
        }
        
    }
    
    public void iniciarTiposContadores(){
        
        this.crearTipoContador(Constants.TIPO_GENERO);
        this.crearTipoContador(Constants.TIPO_ESTUDIO);
        this.crearTipoContador(Constants.TIPO_MODO);
        this.crearTipoContador(Constants.TIPO_FRANQUICIA);
        this.crearTipoContador(Constants.TIPO_TEMA);
        
    }
    
    public void imprimirContadores(){
        System.out.println("Contadores de Genero: ");
        this.imprimirTipoContador(Constants.TIPO_GENERO);
        System.out.println("Contadores de Estudio: ");
        this.imprimirTipoContador(Constants.TIPO_ESTUDIO);
        System.out.println("Contadores de Modo: ");
        this.imprimirTipoContador(Constants.TIPO_MODO);
        System.out.println("Contadores de Franquicia: ");
        this.imprimirTipoContador(Constants.TIPO_FRANQUICIA);
        System.out.println("Contadores de Tema: ");
        this.imprimirTipoContador(Constants.TIPO_TEMA);
    }
    
    public void procesarJuegosJugado(){
        
        for (int i=0; i< this.juegosJugados.size();i++){
            
            String juego= juegosJugados.get(i);
            
            List<String> listaGeneros=  OntologyAccess.getPropiedadesJuegosObject(juego , Constants.PROPIEDAD_GENERO);                        
            
            List<String> listaEstudio=  OntologyAccess.getPropiedadesJuegosData(juego , Constants.PROPIEDAD_ESTUDIO);
                                    
            List<String> listaModo=  OntologyAccess.getPropiedadesJuegosData(juego , Constants.PROPIEDAD_MODO);                       
            
            List<String> listaFranquicia=  OntologyAccess.getPropiedadesJuegosData(juego , Constants.PROPIEDAD_FRANQUICIA);
            
            List<String> listaTema=  OntologyAccess.getPropiedadesJuegosObject(juego , Constants.PROPIEDAD_TEMA);
            
            if(Constants.MODO_DEBUG){
                System.out.println("Generos de " + juego);

                this.imprimeList(listaGeneros);

                System.out.println("Estudio de " + juego);

                this.imprimeList(listaEstudio);

                System.out.println("Modo de " + juego);

                this.imprimeList(listaModo);

                System.out.println("Franquicia de " + juego);

                this.imprimeList(listaFranquicia);
                
                System.out.println("Temas de " + juego);

                this.imprimeList(listaTema);
            }
            
            int estrella= (int)(this.estrellas.get(juego)==null?0:this.estrellas.get(juego));
            int valor=0;
            if(estrella < Constants.PESO_ESTELAR.size()){
                valor=(int)(Constants.PESO_ESTELAR.get(estrella));
            }
            
            this.aumentaContadorLista(Constants.TIPO_GENERO, listaGeneros, valor);
            this.aumentaContadorLista(Constants.TIPO_ESTUDIO, listaEstudio,valor);
            this.aumentaContadorLista(Constants.TIPO_MODO, listaModo,valor);
            this.aumentaContadorLista(Constants.TIPO_FRANQUICIA, listaFranquicia,valor);
            this.aumentaContadorLista(Constants.TIPO_TEMA, listaTema,valor);
                        
        }
            
            imprimirContadores();
    }
    //Metodo que obtiene los juegos y le retira los juegos comprados
    public void obtenerCandidatos(){
        
        try{
            this.juegos=(ArrayList<String>)OntologyAccess.getJuegos();
            this.juegosCandidatos.addAll(this.juegos);
            this.juegosCandidatos.removeAll(this.juegosComprados);
            
            if (!Configuracion.CONSIDERAR_JUEGOS_JUGADOS)
                this.juegosCandidatos.removeAll(this.juegosJugados);
            
            //Obtengo solo aquellos que sean de la consola pedida por el usuario
            //Si no ha elegido consola en particular
            if (!this.consolas.isEmpty()){
                for (int i=0; i<this.juegosCandidatos.size();i++){
                
                    List<String> consolasDeJuego= OntologyAccess.getPropiedadesJuegosObject( this.juegosCandidatos.get(i) , Constants.PROPIEDAD_CONSOLA);                                        
                    System.out.println("Consolas del juego: " + this.juegosCandidatos.get(i));
                    this.imprimeList(consolasDeJuego);
                    if (!consolasDeJuego.removeAll(this.consolas)){
                        this.juegosCandidatos.remove(i);
                        i--;
                    }
                }
            }
           
            if(Constants.MODO_DEBUG){
            
                System.out.println("Juegos: ");
                this.imprimeList(juegos);

                System.out.println("Juegos candidatos: ");
                this.imprimeList(juegosCandidatos);
            }
            
        }catch(Exception e){
            System.out.println(e.getMessage());
        }
    }
    
    public int calculaValorJuego(String juego){
        
        int valor=0;
        
        //GENERO
        List<String> generos= OntologyAccess.getPropiedadesJuegosObject(juego, Constants.PROPIEDAD_GENERO);
        for (int i=0; i<generos.size();i++){
            int agregado=Math.round(Constants.PESO_GENERO*this.getValorContador(Constants.TIPO_GENERO, generos.get(i))/generos.size());
            valor= valor + agregado;
            mExplicacion.realizaExplicacion(juego, Constants.TIPO_GENERO, generos.get(i),agregado, Constants.PESO_GENERO, " juegos del género");
        }
        //FRANQUICIA
        List<String> franquicias= OntologyAccess.getPropiedadesJuegosData(juego, Constants.PROPIEDAD_FRANQUICIA);
        for (int i=0; i<franquicias.size();i++){
            int agregado=Constants.PESO_FRANQUICIA*this.getValorContador(Constants.TIPO_FRANQUICIA, franquicias.get(i));
            valor= valor + agregado;
            mExplicacion.realizaExplicacion(juego, Constants.TIPO_FRANQUICIA, franquicias.get(i),agregado, Constants.PESO_FRANQUICIA, " juegos de la franquicia");
        }
        //ESTUDIO
        List<String> estudios= OntologyAccess.getPropiedadesJuegosData(juego, Constants.PROPIEDAD_ESTUDIO);
        for (int i=0; i<estudios.size();i++){
            int agregado=Constants.PESO_ESTUDIO*this.getValorContador(Constants.TIPO_ESTUDIO, estudios.get(i));
            valor= valor + agregado;
            mExplicacion.realizaExplicacion(juego, Constants.TIPO_ESTUDIO, estudios.get(i),agregado, Constants.PESO_ESTUDIO, " juegos del estudio");
        }
        //MODO
        List<String> modos= OntologyAccess.getPropiedadesJuegosData(juego, Constants.PROPIEDAD_MODO);
        for (int i=0; i<modos.size();i++){
            int agregado=Constants.PESO_MODO*this.getValorContador(Constants.TIPO_MODO, modos.get(i));
            valor= valor + agregado;            
            mExplicacion.realizaExplicacion(juego, Constants.TIPO_MODO, modos.get(i),agregado, Constants.PESO_MODO, " juegos con el modo");
        }
        //TEMA
        List<String> temas= OntologyAccess.getPropiedadesJuegosObject(juego, Constants.PROPIEDAD_TEMA);
        for (int i=0; i<temas.size();i++){
            int agregado=Constants.PESO_TEMA*this.getValorContador(Constants.TIPO_TEMA, temas.get(i));
            valor= valor + agregado;
            mExplicacion.realizaExplicacion(juego, Constants.TIPO_TEMA, temas.get(i),agregado, Constants.PESO_TEMA, " juegos con el tema");
        }
        
        
        return valor;
    }
    
    //Funcion para obtener el valor de cada juego
    public void calculaValoresCandidatos(){
        
        this.crearTipoContador(Constants.VALOR_JUEGO);
        for (int i=0; i<this.juegosCandidatos.size();i++){
            String juego=this.juegosCandidatos.get(i);
            
            this.crearContador(Constants.VALOR_JUEGO, juego);
            this.setContador(Constants.VALOR_JUEGO, juego, this.calculaValorJuego(juego));
            
        }
        System.out.println("CANDIDATOS Y VALORES:");
        this.imprimirTipoContador(Constants.VALOR_JUEGO);
    }
    
    public void quicksort(List<String> array, int start, int end)
{
        int i = start;                          // index of left-to-right scan
        int k = end;                            // index of right-to-left scan

        if (end - start >= 1)                   // check that there are at least two elements to sort
        {
                int pivot = this.getValorContador(Constants.VALOR_JUEGO , array.get(start)); //array[start];       // set the pivot as the first element in the partition

                while (k > i)                   // while the scan indices from left and right have not met,
                {
                        while (this.getValorContador(Constants.VALOR_JUEGO ,array.get(i)) >= pivot && i <= end && k > i ){//(array[i] <= pivot && i <= end && k > i){  // from the left, look for the first
                                i++;                                   // element greater than the pivot
                        }
                        
                        while (this.getValorContador(Constants.VALOR_JUEGO ,array.get(k))< pivot && k >= start && k >= i){//(array[k] > pivot && k >= start && k >= i){ // from the right, look for the first
                            k--;                                        // element not greater than the pivot
                        }
                        if (k > i)                                       // if the left seekindex is still smaller than
                                swap(array, i, k);                      // the right index, swap the corresponding elements
                }
                swap(array, start, k);          // after the indices have crossed, swap the last element in
                                                // the left partition with the pivot 
                quicksort(array, start, k - 1); // quicksort the left partition
                quicksort(array, k + 1, end);   // quicksort the right partition
        }
        else    // if there is only one element in the partition, do not do any sorting
        {
                return;                     // the array is sorted, so exit
        }
}
    
    public void swap(List<String> array, int index1, int index2) 
// pre: array is full and index1, index2 < array.length
// post: the values at indices 1 and 2 have been swapped
{
	String temp = array.get(index1);           // store the first value in a temp
	array.set(index1,array.get(index2));      // copy the value of the second into the first
	array.set(index2, temp);               // copy the value of the temp into the second
}

    //Se ordena la lista de candidatos de mayor a menor
    public void ordenarCandidatos(){
        
        this.quicksort(this.juegosCandidatos, 0, this.juegosCandidatos.size()-1);
        
    }
    
    //Se obtiene los n mejores
    public void obtenerJuegosRecomendados(int numSugeridos){
        
        for (int i=0; i<numSugeridos && i<this.juegosCandidatos.size();i++){
            
            //Se verifica que el valor obtenido sea mayor al minimo
            if( this.getValorContador(Constants.VALOR_JUEGO, this.juegosCandidatos.get(i))<Constants.MINIMO_CANDIDATO)
                break;
            
            this.juegosRecomendados.add(this.juegosCandidatos.get(i));
        }
    }
    
    
    //Funcion de ejecución que devuelva n juegos top según preferencias
    
    public ArrayList<String> obtenerSugerencias(){
        
        ArrayList<String> juegosSugeridos= new ArrayList<String>();
        
        //Se inician los tipos contadores
        
        this.iniciarTiposContadores();
        
        //if(Constants.MODO_DEBUG)
        //this.usuario="avr92"; //para pruebas
        
        //this.juegosJugados= (ArrayList)OntologyAccess.getJuegosJugadosUsuario(this.usuario);
                        
        //this.juegosComprados= (ArrayList)OntologyAccess.getJuegosCompradosUsuario(this.usuario);
        
        if(Constants.MODO_DEBUG){
            
            System.out.println("Juegos jugados de " + this.usuario);
        
            this.imprimeList(this.juegosJugados);
        
            System.out.println("Juegos comprados de " + this.usuario);

            this.imprimeList(this.juegosComprados);
        }
        
        //Se realiza el conteo respectivo de los juegos jugados
        this.procesarJuegosJugado();
        //Se inicia el modulo de explicación
        mExplicacion= new ModuloExplicacion();
        mExplicacion.prepararModuloExplicacon(contadores, this.juegosJugados.size());
        //Se obtienen los candidatos a juego sugerido
        this.obtenerCandidatos();
        //Se calculan los valores de candidatos
        this.calculaValoresCandidatos();
        //Se ordenan los candidatos
        this.ordenarCandidatos();
        if(Constants.MODO_DEBUG){
            System.out.println("Juegos candidatos ordenados: ");

            this.imprimeList(this.juegosCandidatos);
        }
        //Se obtienen los n mejores
        this.obtenerJuegosRecomendados(Configuracion.NUMERO_SUGERENCIAS);
        System.out.println("Los juegos recomendados para usted son:");

        this.imprimeList(this.juegosRecomendados);
        
        //System.out.println(mExplicacion.obtenerExpliacion(this.juegosRecomendados.get(0)));
        return juegosRecomendados;
    }
    
    public void imprimeList(List<String>lista){        
        if (Constants.MODO_DEBUG)
            for (int i=0; i<lista.size(); i++)
                            System.out.println( lista.get(i) );
        
    }
    
    public void ingresarJuegoJugado(String juego, int estrellas){
        
        this.juegosJugados.add(juego);
        this.estrellas.put(juego, estrellas);
    }
    
    public void ingresarJuegoComprado(String juego){
        this.juegosComprados.add(juego);
        
    }
    
    public void removerJuegoJugado(String juego){
        
        this.juegosJugados.remove(juego);
    }
    
    public void removerJuegoComprado(String juego){
        this.juegosComprados.remove(juego);
        
    }
    
    public void ingresarConsola(String consola){
        this.consolas.add(consola);
        
    }
    
    public String obtenerExplicacionJuego(String juego){
        
        return this.mExplicacion.obtenerExpliacion(juego);
    }
}
